home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-02-11 | 33.3 KB | 859 lines | [TEXT/KAHL] |
- /* See the file Distribution for distribution terms.
- (c) Copyright 1994 Ari Halberstadt */
-
- /*
- Description
- -----------
-
- ThreadLib implements nonpreemptive multiple thread execution within
- a single application. It does not require any extensions, should work
- with all Macintosh models (from the Plus on up), and works with
- systems 6.0 (tested on 6.0.5) under Finder or MultiFinder, and
- system 7.0. ThreadLib compiles into a small library of about 4.5K,
- so it won't add much overhead to your application. A small test
- application demonstrates how threads are used threads. Best of all,
- the source code is free.
-
- Every thread has its own stack and exception handling environment;
- all other global application data is shared by the threads. Context
- switches are very efficient since they involve only a few operations
- to save the current thread's state, followed by a longjmp to the new
- thread and restoration of the new thread's exception environment. There
- are also no restrictions on the objects that can be allocated on a
- thread's stack.
-
- The rest of this file is heavily commented, so you should be able to
- follow how threads are implemented. Each external function has a
- description of its parameters and what it does.
-
- Notes and Warnings
- ------------------
-
- The thread library must be kept in memory so that longjmp will work.
- The segment containing the thread library must not be unloaded while
- there are any threads.
-
- WARNING: For all threads other than the main thread, some Maintosh Toolbox
- routines may not work correctly if the stack is not between the region of
- memory defined by the low-memory globals CurStackBase and ApplLimit.
- Possibly prohibited are some QuickDraw calls, but I don't actually know
- which Toolbox routines will fail. Some simple tests I ran created a dialog
- with a progress bar; created, opened, read and wrote files; created a
- resource file and added resources to it; allocated memory; and did various
- other operations, all successfully and without problems. If you do encounter
- some problems, you may have to define THREAD_SAVE_GLOBALS in "ThreadLib.h".
- This will enable code to save and restore the same low-memory globals that
- Apple's Thread Manager v1.2 saves and restores during context switches.
- I don't know if defining THREAD_SAVE_GLOBALS will actually do any good, but
- it's worth a try. Since the main thread uses the application's stack, there
- are no restrictions on the Toolbox routines that the main thread may call.
- I am interested in whether you encounter (or don't encounter) limitations
- to Toolbox calls, and would like to know under what conditions the
- limitations arise.
-
- WARNING: Using the THINK C debugger to trace through context switches
- may result in corruption of the application's heap followed by a nasty
- crash. I've used TMON Professional to successfully trace through the
- context switches, and other low-level debuggers (like MacsBug) should
- also work.
-
- NOTE: The trap StackSpace will always return the space remaining in the
- application's stack. This is not the same as the space remaining in
- the current thread's stack. Likewise, using ApplLimit to determine the
- end of a thread's stack will produce incorrect results. StackSpace will,
- however, continue to return the correct value when the main thread
- is executing. To determine the amount of free stack space in a thread,
- use ThreadStackSpace.
-
- Error Handling
- --------------
-
- Errors are handled using my exception library. There are ample comments
- in the file ExceptionLib.c to help you figure out how to use exceptions.
- You may need to modify how errors are handled in order to incorporate
- ThreadLib into your application. The exception handling library uses the
- same macro names as the exception handling macros provided with the THINK
- Class Library (except for the CLEANUP macro), so adapting the thread
- library to work with the TCL should not be too difficult. For other
- applications, you may need to write wrapper functions which call the
- thread library. These functions would use the exception library to catch
- the error and then they would return an error code to the caller.
- Following is an example of a wrapper for ThreadBegin (the letter E stands
- for "Error"):
-
- OSErr EThreadBegin(ThreadProcType entry, ThreadProcType suspend,
- ThreadProcType resume, void *data, size_t stack_size,
- ThreadHandle *result)
- {
- OSErr err = noErr;
-
- TRY {
- *result = ThreadBegin(entry, suspend, resume, data, stack_size);
- } CATCH {
- *result = NULL;
- err = FailReason();
- NOPROPAGATE;
- } ENDTRY;
- return(err);
- }
-
- other wrapper functions would have a similar form.
-
- To Do
- -----
-
- - EventAvail is a very slow trap to call from within a thread.
- I would like to use a faster method to determine if an event is
- available, but just testing the EventQueue low-memory global
- is not sufficient since update and activate events aren't posted
- to the event queue. Activate events can be detected by checking
- the low-memory globals CurActivate and CurDeactive, but how
- can update events be detected without calling EventAvail?
- If I ever figure out a solution to this problem, then ThreadSchedule
- will no longer give time to other applications, so you'll have to
- allow other applications to run by calling WNE, GNE, or EventAvail
- from within a thread. (Or pass 0-10 ticks for the sleep parameter
- when calling ThreadYield in the main thread, though that would
- probably be less efficient than periodically calling EventAvail.)
-
- - It shouldn't be too difficult to add preemptive multitasking
- to this library. Someday maybe I'll get around to it.
-
- Credits
- -------
-
- Some ideas on how to use setjmp/longjmp to swap stacks were adapted
- from the source for Task Manager v2.2.1 by Michael Hecht
- (Michael_Hecht@mac.sas.com), available at the info-mac archives
- and various other sites.
-
- Thanks to Anton Rang (rang@icicle.winternet.mpls.mn.us) who responded
- to my query on Comp.sys.mac.programmer on how to disable the stack
- sniffer VBL task. (Several other people also responded, but Anton Rang's
- reply was the first to arrive.)
-
-
- Epilogue
- --------
-
- If it took me, a single programmer, 6 days to get threads up and
- running (and that not even full-time), why did it take Apple many
- years and a big fancy extension to get around to implementing
- threads? This hack isn't very difficult. I had something sort-of
- working the first day, but it took me this long to get it reasonably
- stable and to put in all these verbose comments. It would have been
- even easier to implement had I had access to proprietary Apple
- information. If you look at the Thread Manager extension, it's really
- very simple, and it doesn't do much more than what this library does.
- (I developed this library prior to examining what the Thread Manager
- does and did not try to copy the Thread Manager.)
-
- 94/02/10 aih - got threads to work without crashing! yay! :-)
- 94/02/04 aih - created */
-
- #include <limits.h>
-
- #if WINTER_SHELL
- #include <SysEqu.h>
- #include "LowMemLib.h"
- #include "MemoryLib.h"
- #include "ThreadLib.h"
- #else /* WINTER_SHELL */
- #include "ThreadUtil.h"
- #include "ExceptionLib.h"
- #include "ThreadLib.h"
- #endif /* WINTER_SHELL */
-
- /*
- Following is a list of functions, constants, and types defined in
- Winter Shell. So that this library can be used without Winter Shell,
- simplified replacements are provided in ThreadUtil.c.
-
- memclr fills memory with zeros
- HandleValidSize validates a handle
- HandleBeginClear allocates a new handle (like NewHandleClear)
- HandleEnd disposes of a handle (like DisposeHandle)
- PtrValid validates a pointer
- PtrBegin allocates a new pointer (like NewPtr)
- PtrEnd disposes of a pointer (like DisposePtr)
- GetCurStackBase returns value of CurStackBase low-memory global
- SetStkLowPt sets value of StkLowPt low-memory global
- LLHAppend appends an item to the end of a linked list of handles
- LLHDelete deletes an item from a linked list of handles
- LLHNext returns the next item in a linked list of handles
- TicksType typedef'd to unsigned long
- require/check/ensure assertion macros, similar to the standard assert macro
- */
-
- #if ! defined(THINK_C) || THINK_C != 5
- /* The register ordering within the jmp_buf type is compiler dependent.
- It should be straightforward to modify this for MPW, and less so
- for a native PowerPC version (though perhaps still possible). For
- MPW and later versions of THINK C you should check the setjmp header
- for the definition of jmp_buf and also check the instructions used to
- save and restore the registers (in THINK C they're inline definitions).
- We only really care about accessing and modifying registers a6 and a7
- in the jump buffer. Also, as currently implemented, setjmp and longjmp
- must not move memory (since they're passed a pointer dereferenced
- from a handle).
-
- If you fix this for THINK C 6.0 or MPW, please let me know what
- changes were necessary so I can incorporate them into the next
- release. */
- #error "only tested with THINK C 5.0.4"
- #endif
-
- #if defined(THINK_C) && __option(profile)
- /* The THINK C profiler assumes a single execution stack and won't
- work correctly if threads are used. Other profilers may or may
- not work correctly; use them at your own risk. */
- #error "won't work with THINK C profiler"
- #endif
-
- /* register ordering within jmp_buf (only for THINK C 5.0.4) */
- enum {
- d3, d4, d5, d6, d7,
- a1, a2, a3, a4, a6, a7
- /* may be followed by extra floating point registers */
- };
-
- /* values returned by calls to setjmp */
- typedef enum { THREAD_SAVE, THREAD_RUN };
-
- /* structure describing state of thread library */
- typedef struct {
- short count; /* number of threads in queue */
- ThreadHandle queue; /* queue of threads */
- ThreadHandle active; /* currently active thread */
- ThreadHandle main; /* main thread */
- ThreadHandle dispose; /* thread to dispose of */
- } ThreadStateType;
-
- /* state of thread library */
- ThreadStateType gThread;
-
- /*----------------------------------------------------------------------------*/
- /* validation */
- /*----------------------------------------------------------------------------*/
-
- /* true if a valid thread */
- Boolean ThreadValid(ThreadHandle thread)
- {
- if (! HandleValidSize(thread, sizeof(ThreadType))) return(false);
- if (thread == gThread.main) {
- if ((**thread).stack) return(false);
- if ((**thread).entry) return(false);
- }
- else {
- if (! PtrValid((**thread).stack)) return(false);
- if (! (**thread).entry) return(false);
- }
- return(true);
- }
-
- /*----------------------------------------------------------------------------*/
- /* thread queue */
- /*----------------------------------------------------------------------------*/
-
- /* ThreadCount returns the number of threads in the queue. */
- short ThreadCount(void)
- {
- return(gThread.count);
- }
-
- /* ThreadMain returns the main thread. */
- ThreadHandle ThreadMain(void)
- {
- return(gThread.main);
- }
-
- /* ThreadActive returns the currently active thread. */
- ThreadHandle ThreadActive(void)
- {
- return(gThread.active);
- }
-
- /* ThreadFirst returns the first thread in the queue of threads. */
- ThreadHandle ThreadFirst(void)
- {
- return(gThread.queue);
- }
-
- /* ThreadNext returns the next thread in the circular queue of threads. */
- ThreadHandle ThreadNext(ThreadHandle thread)
- {
- return(LLHNext(thread) ? LLHNext(thread) : gThread.queue);
- }
-
- /*----------------------------------------------------------------------------*/
- /* information about a thread */
- /*----------------------------------------------------------------------------*/
-
- /* ThreadStackSpace returns the amount of stack space remaining in the
- specified thread. There are at least the returned number of bytes
- between the thread's stack pointer and the bottom of the thread's
- stack, though slightly more space may be available to the application
- due to overhead from the thread library. */
- size_t ThreadStackSpace(ThreadHandle thread)
- {
- size_t result;
- Ptr stack_bottom;
- jmp_buf registers;
-
- require(ThreadValid(thread));
- if (thread == gThread.main)
- stack_bottom = GetApplLimit();
- else
- stack_bottom = (**thread).stack;
- if (thread == gThread.active) {
- (void) setjmp(registers);
- check((Ptr) registers[a7] >= stack_bottom);
- result = (Ptr) registers[a7] - stack_bottom;
- }
- else {
- check((Ptr) (**thread).env[a7] >= stack_bottom);
- result = (Ptr) (**thread).env[a7] - stack_bottom;
- }
- ensure(result >= 0);
- return(result);
- }
-
- /*----------------------------------------------------------------------------*/
- /* segmentation support */
- /*----------------------------------------------------------------------------*/
-
- /* ThreadStackFrame returns information about the specified thread's
- stack and stack frame. This information is needed for executing
- a stack trace during automatic segment unloading in SegmentLib.c.
- You should never need to call this function. This function will
- work correctly even if no threads exist. */
- void ThreadStackFrame(ThreadHandle thread, ThreadStackFrameType *frame)
- {
- jmp_buf registers;
-
- require(! thread || ThreadValid(thread));
- memclr(frame, sizeof(ThreadStackFrameType));
- if (thread == gThread.main) {
- /* The main thread always uses the application's stack, so we
- just use the current values of the stack base and registers.
- This is the default case if there are no threads (in which
- case 'thread' and gThread.main are both null). */
- (void) setjmp(registers);
- frame->stack_top = GetCurStackBase();
- frame->stack_bottom = (Ptr) registers[a7];
- frame->register_a6 = *(Ptr *) registers[a6];
- }
- else if (thread == gThread.active) {
- /* The active thread uses a stack allocated from a pointer in the
- heap, but registers a6 and a7 point into this stack so we
- can just use their current values. */
- check(thread != NULL);
- (void) setjmp(registers);
- frame->stack_top = (**thread).stack + PtrSize((**thread).stack);
- frame->stack_bottom = (Ptr) registers[a7];
- frame->register_a6 = *(Ptr *) registers[a6];
- }
- else {
- /* All other threads are inactive and use their own private stack,
- so we use the values of registers a6 and a7 that were saved
- when the thread was suspended. */
- check(thread != NULL);
- frame->stack_top = (**thread).stack + PtrSize((**thread).stack);
- frame->stack_bottom = (Ptr) (**thread).env[a7];
- frame->register_a6 = (Ptr) (**thread).env[a6];
- }
- ensure(! frame->register_a6 ||
- (frame->stack_bottom <= frame->register_a6 &&
- frame->register_a6 <= frame->stack_top));
- ensure((Ptr) 0 < frame->stack_bottom && frame->stack_bottom <= frame->stack_top);
- }
-
- /*----------------------------------------------------------------------------*/
- /* thread scheduling and context switching */
- /*----------------------------------------------------------------------------*/
-
- /* ThreadSchedule returns the next thread to activate. Threads are maintained
- in a queue and are scheduled in a round-robbin fashion. Starting with the
- current thread, the queue of threads is searched for the next thread whose
- wake time has arrived. The first such thread found is returned.
-
- In addition to the round-robbin scheduling shared with all threads, the
- main thread will also be activated if any events are pending in the event
- queue. The application can then immediately handle the events, allowing
- the application to remain responsive to user actions such as mouse clicks.
- The main thread will also be activated if no other threads are scheduled
- for activation, which allows the application either to continue with
- its main processing or to call WaitNextEvent and sleep until a thread
- needs to be activated or some other task or event needs to be handled.
- Since the thread scheduler calls EventAvail, background applications
- will continue to receive processing time, even if the main thread is
- never activated while some compute intensive thread is executing. But,
- since EventAvail can be a slow trap (especially when it yields the
- processor to another application), it is only executed every few ticks.
- Note: if I figure out a faster way to test for events then the call
- to EventAvail will be removed, and background applications won't
- get time when ThreadSchedule is called. */
- ThreadHandle ThreadSchedule(void)
- {
- #define EVENT_INTERVAL (6) /* number of ticks between calls to EventAvail */
- static TicksType lastEvent;/* last time we called EventAvail */
- ThreadHandle newthread; /* thread to switch to */
- EventRecord event; /* for checking for events */
- TicksType ticks; /* current tick count */
-
- require(ThreadValid(gThread.active));
- if (gThread.active != gThread.main &&
- TickCount() - lastEvent >= EVENT_INTERVAL &&
- EventAvail(everyEvent, &event))
- {
- /* an event is pending, so activate main thread */
- lastEvent = TickCount();
- newthread = gThread.main;
- }
- else {
- /* search for a thread to activate in round-robbin fashion */
- ticks = TickCount();
- newthread = ThreadNext(gThread.active);
- while (newthread != gThread.active && (**newthread).wake > ticks)
- newthread = ThreadNext(newthread);
- if (newthread == gThread.active && (**newthread).wake > ticks) {
- /* no thread needs to be woken up, so activate main thread */
- newthread = gThread.main;
- }
- }
- ensure(ThreadValid(newthread));
- return(newthread);
- }
-
- /* ThreadSave saves the context of the active thread. It is called before
- a thread is suspended, but after setjmp has saved the CPU's context for
- the thread. */
- static void ThreadSave(void)
- {
- require(ThreadValid(gThread.active));
-
- /* save exception state (ExceptionCopy won't move memory) */
- ExceptionCopy(&gException, &(**gThread.active).exception);
-
- #if THREAD_SAVE_GLOBALS
- /* save low-memory globals */
- (**gThread.active).heapEnd = *(Ptr *) HeapEnd;
- (**gThread.active).applLimit = *(Ptr *) ApplLimit;
- (**gThread.active).hiHeapMark = *(Ptr *) HiHeapMark;
- #endif /* THREAD_SAVE_GLOBALS */
-
- /* call the application's suspend function */
- if ((**gThread.active).suspend)
- (**gThread.active).suspend((**gThread.active).data);
- }
-
- /* ThreadRestore restores the context of the active thread. It is called
- before a thread resumes execution, but after the thread's stack has
- been restored. */
- static void ThreadRestore(void)
- {
- require(ThreadValid(gThread.active));
-
- /* Restore the exception state for the thread. The first
- time this is executed it clears and resets the exception
- state, since the exception field of the thread structure
- is initially filled with nulls. The exception state must
- be cleared before ThreadStart is called, since ThreadStart
- has its own exception handlers. (ExceptionCopy won't move
- memory.) */
- ExceptionCopy(&(**gThread.active).exception, &gException);
-
- #if THREAD_SAVE_GLOBALS
- /* Set the low-memory globals for the thread. We bypass any
- traps or glue (like SetApplLimit) to keep the OS from
- preventing us from changing these globals. */
- *(Ptr *) HeapEnd = (**gThread.active).heapEnd;
- *(Ptr *) ApplLimit = (**gThread.active).applLimit;
- *(Ptr *) HiHeapMark = (**gThread.active).hiHeapMark;
- #endif /* THREAD_SAVE_GLOBALS */
-
- /* dispose of the memory allocated for the previous thread (see ThreadEnd) */
- if (gThread.dispose) {
- PtrEnd((**gThread.dispose).stack);
- HandleEnd(gThread.dispose);
- gThread.dispose = NULL;
- }
-
- /* call the application's resume function */
- if ((**gThread.active).resume)
- (**gThread.active).resume((**gThread.active).data);
- }
-
- /* ThreadActivate activates the specified thread. The context switch is
- accomplished by saving the current stack, restoring the new thread's stack,
- and then calling longjmp, which jumps to the environment saved with setjmp
- when the thread being activated was last suspended. We don't have to do any
- assembly language glue since setjmp saved the value of the stack pointer,
- which at the time of the call to setjmp pointed somewhere in the thread's
- stack. The longjmp instruction will restore the value of the stack pointer
- and will jump to the statement from which to resume the thread. Longjmp
- also handles all register saving and restoring, freeing us from the
- maintenance problems of supporting different register sets (e.g., floating
- point registers). */
- void ThreadActivate(ThreadHandle thread)
- {
- ThreadHandle active;
-
- require(ThreadValid(gThread.active));
- require(ThreadValid(thread));
- active = gThread.active;
- if (thread != active) {
- if (setjmp((**active).env) == THREAD_SAVE) {
- /* the thread is being deactivated, so save the active thread's
- context */
- ThreadSave();
-
- /* Jump to the specified thread. This suspends the current thread
- and returns at the setjmp statement above (unless this is the
- first time the thread is being executed, in which case it
- returns at the setjmp statement in ThreadBegin). The contents
- of the stack will be correct as soon as the longjmp has
- completed, but ThreadRestore must be called before the
- thread can resume. */
- gThread.active = thread;
- longjmp((**thread).env, THREAD_RUN);
- check(false); /* doesn't return */
- }
- else {
- /* the thread is being activated, so restore the thread's context */
- ThreadRestore();
- }
- }
- }
-
- /* ThreadYield activates the next scheduled thread as determined by
- ThreadSchedule. The 'sleep' parameter specifies the maximum amount
- of time that the current thread can remain inactive. */
- void ThreadYield(TicksType sleep)
- {
- TicksType ticks;
-
- /* Set active thread's wakeup time before we run the scheduler and before
- the active thread is suspended. (We're careful with overflow since the
- sleep parameter could be ULONG_MAX.) */
- require(ThreadValid(gThread.active));
- require(sleep >= 0);
- ticks = TickCount();
- if (sleep > ULONG_MAX - ticks)
- (**gThread.active).wake = ULONG_MAX;
- else
- (**gThread.active).wake = ticks + sleep;
- ThreadActivate(ThreadSchedule());
- }
-
- /* Return the maximum time till the next call to ThreadYield. The interval
- is computed by subtracting the current time from each thread's wake time,
- giving the amount of time that each thread can remain inactive. The
- minimum of these times gives the maximum amount of time till the next
- call to ThreadYield. The wake time of the current thread is ignored,
- since the thread is already active. You can use the returned value
- to determine the maximum sleep value to pass to WaitNextEvent. */
- TicksType ThreadYieldInterval(void)
- {
- ThreadHandle thread; /* for iterating through list of threads */
- TicksType interval; /* interval till next call to ThreadYield */
- TicksType elapsed; /* time elapsed since a thread was last executed */
- TicksType ticks; /* current tick count */
-
- ticks = TickCount();
- interval = LONG_MAX;
- for (thread = gThread.queue; thread && interval; thread = LLHNext(thread)) {
- if (thread != gThread.active) {
- if ((**thread).wake <= ticks)
- interval = 0;
- else if ((**thread).wake - ticks < interval)
- interval = (**thread).wake - ticks;
- }
- }
- ensure(interval >= 0);
- return(interval);
- }
-
- /*----------------------------------------------------------------------------*/
- /* thread creation and destruction */
- /*----------------------------------------------------------------------------*/
-
- /* ThreadEnd removes the thread from the queue and disposes of the memory
- allocated for the thread. If the thread is the active thread then the
- next thread in the queue is activated. All threads (other than the main
- thread) must be disposed of before the main thread can be disposed of. */
- void ThreadEnd(ThreadHandle thread)
- {
- ThreadHandle newthread = NULL;/* thread to activate if disposing of the active thread */
-
- if (thread) {
-
- if (thread == gThread.main) {
- /* disposing of main thread, so clear globals */
- check(gThread.active == thread);
- check(gThread.count == 1);
- gThread.main = gThread.active = NULL;
- }
- else if (thread == gThread.active) {
- /* disposing of the active thread, so activate the next scheduled
- thread, or the main thread if the next scheduled thread is the
- active thread */
- newthread = ThreadSchedule();
- if (newthread == gThread.active)
- newthread = gThread.main;
- }
- check(! newthread || newthread != gThread.active);
-
- /* remove thread from queue */
- gThread.queue = LLHDelete(gThread.queue, thread);
- check(gThread.count > 0);
- gThread.count--;
- check(gThread.count > 0 ? gThread.queue != NULL : ! gThread.queue);
-
- /* activate the next thread (we don't call ThreadActivate since
- there's no need to save the now-defunct thread's state) */
- if (thread == gThread.active && newthread) {
-
- /* We're disposing of the active thread, but we can't dispose of
- the thread's stack since we're still using that stack. So
- we delay disposal of the thread until the next thread is
- activated; the thread will be disposed of in ThreadRestore,
- which is executed when the next thread is resumed. */
- check(! gThread.dispose);
- gThread.dispose = thread;
- gThread.active = newthread;
- longjmp((**newthread).env, THREAD_RUN);
- check(false); /* doesn't return */
- }
- else {
- /* dispose of the memory allocated for the thread */
- PtrEnd((**thread).stack);
- HandleEnd(thread);
- }
- }
- }
-
- /* ThreadBeginMain creates the main application thread. You must call this
- function before creating any other threads with ThreadBegin. The
- 'resume', 'suspend', and 'data' parameters have the same meanings
- as the parameters to ThreadBegin.
-
- There are several important differences between the main thread and
- all subsequently created threads.
-
- - The main thread is responsible for handling events sent to the
- application, and is therefore scheduled differently than other threads;
- see ThreadSchedule for details.
-
- - While other threads don't begin executing until they're scheduled to
- execute, the main thread is made the active thread and starts to run as
- soon as ThreadBeginMain returns.
-
- - Since other threads have a special entry point, they are automatically
- disposed of when that entry point returns. The main thread, lacking
- any special entry point, must be disposed of by the application. You
- should call ThreadEnd, passing it the thread returned by ThreadBeginMain
- before exiting your application.
-
- - The main thread uses the application's stack and context; no private
- stack is allocated for the main thread. Initially, there is therefore
- no need to change any values to start executing the thread, and
- no special entry point is required. But, like all other threads, the main
- thread's context will be saved whenever it is suspended to allow another
- thread to execute, and its context will be restored when it is resumed.
- */
- ThreadHandle ThreadBeginMain(ThreadProcType suspend, ThreadProcType resume,
- void *data)
- {
- volatile ThreadHandle thread = NULL; /* the new thread */
-
- require(! gThread.main);
- TRY {
-
- /* allocate thread structure */
- thread = HandleBeginClear(sizeof(ThreadType));
-
- /* initialize thread structure */
- (**thread).suspend = suspend;
- (**thread).resume = resume;
- (**thread).data = data;
-
- /* save values of low-memory globals */
- #if THREAD_SAVE_GLOBALS
- (**thread).heapEnd = *(Ptr *) HeapEnd;
- (**thread).applLimit = *(Ptr *) ApplLimit;
- (**thread).hiHeapMark = *(Ptr *) HiHeapMark;
- #endif /* THREAD_SAVE_GLOBALS */
-
- /* now that the thread is ready to use, append it to the queue of threads
- so that it can be scheduled for execution */
- gThread.queue = LLHAppend(gThread.queue, thread);
- gThread.count++;
-
- /* make this thread the active and main thread */
- gThread.active = thread;
- gThread.main = thread;
- } CATCH {
- ThreadEnd(thread);
- } ENDTRY;
- ensure(ThreadValid(thread));
- ensure(thread == gThread.main);
- ensure(gThread.active == gThread.main);
- ensure(gThread.queue == gThread.main);
- return(thread);
- }
-
- /* ThreadStart is called when a thread is first executed; it calls the
- thread's entry point and takes care of intercepting exceptions. The
- exception handlers that were set up before the thread was created are on
- a different stack which is not valid (it's swapped out) when the CLEANUP
- handler in ThreadStart gets executed, so we must prevent the exception
- from propagating. When the thread returns it is disposed of and the
- next scheduled thread is activated. */
- static void ThreadStart(void)
- {
- require(ThreadValid(gThread.active));
- TRY {
-
- /* call the thread's entry point */
- (**gThread.active).entry((**gThread.active).data);
-
- } CLEANUP {
-
- /* prevent exceptions from propagating */
- NOPROPAGATE;
-
- /* dispose of the thread and switch to the next scheduled thread */
- ThreadEnd(gThread.active);
-
- } ENDTRY;
- ensure(false); /* never returns */
- }
-
- /* ThreadBegin creates a new thread. You must create the main thread with
- ThreadBeginMain before you can call ThreadBegin. The 'entry' parameter is
- a pointer to a function that is called to start executing the thread. The
- 'suspend' parameter is a pointer to a function called whenever the
- thread is suspended. You can use the 'suspend' function to save
- additional application defined context for the thread. The 'resume'
- parameter is a pointer to a function call whenever the thread is
- resumed. You can use the 'resume' function to save additional application
- defined context for the thread. The 'data' parameter is passed to the
- 'entry', 'suspend', and 'resume' functions and may contain any application
- defined data. The 'stack_size' parameter specifies the size of the stack
- needed by the thread. The requested stack size should be large enough to
- contain all function calls and local variables and parameters. If
- 'stack_size' is zero then the default stack size THREAD_STACK_SIZE
- is used. If your thread crashes (often with a corrupted heap) try
- increasing the thread's stack size.
-
- The new thread is appended to the end of the thread queue, making it
- eligible for scheduling whenever ThreadYield is called. ThreadBegin
- returns immediately after creating the new thread. The thread, however,
- is not executed immediately, but rather is executed whenever it is
- scheduled to execute. At that time, the function specified in the 'entry'
- parameter is called. When the function has returned, the thread is removed
- from the queue of threads and its stack and any private storage allocated
- by ThreadBegin are disposed of. */
- ThreadHandle ThreadBegin(ThreadProcType entry,
- ThreadProcType suspend, ThreadProcType resume,
- void *data, size_t stack_size)
- {
- volatile ThreadHandle thread = NULL; /* the new thread */
- Ptr stack_top = NULL; /* pointer to top of thread's stack */
- Ptr stack = NULL; /* or pointer to thread's stack */
-
- require(ThreadValid(gThread.main));
- require(entry != NULL);
- require(0 <= stack_size);
-
- TRY {
-
- /* allocate thread structure */
- thread = HandleBeginClear(sizeof(ThreadType));
-
- /* initialize thread structure */
- (**thread).entry = entry;
- (**thread).suspend = suspend;
- (**thread).resume = resume;
- (**thread).data = data;
-
- /* The main thread uses the application's regular stack, while
- nonrelocatable blocks are allocated to contain the stacks of
- all other threads. Since all stacks persist until the thread
- that created them terminates, you can create any object you
- require on the stacks, including window records and parameter
- blocks. The main advantage of using separate stacks, however,
- is the speed of context switches. A context switch involves
- only a call to longjmp, and no time consuming saving and
- restoring of stacks is necessary. */
- if (! stack_size)
- stack_size = THREAD_STACK_SIZE;
- stack = PtrBegin(stack_size);
- stack_top = stack + stack_size;
- (**thread).stack = stack;
-
- /* Since all threads other than the main thread use stacks
- allocated in the application's heap, we need to disable the
- stack sniffer VBL task by setting the low-memory global
- variable StkLowPt to 0. Otherwise, the stack sniffer would
- generate system error #28. Thanks to Anton Rang
- (rang@icicle.winternet.mpls.mn.us) for how to disable
- the stack sniffer. */
- SetStkLowPt(NULL);
-
- #if THREAD_SAVE_GLOBALS
- /* Certain low-memory globals divide the stack and heap. We change
- the globals when a thread other than the main thread is activated
- so that certain OS traps will work correctly. */
- (**thread).heapEnd = stack_top;
- (**thread).applLimit = stack_top;
- (**thread).hiHeapMark = stack_top;
- #endif /* THREAD_SAVE_GLOBALS */
-
- /* Setup the new thread's jump environment so that we'll jump
- here when the thread is first activated. */
- if (setjmp((**thread).env) == THREAD_RUN) {
-
- /* We're now executing the *new* thread (I know, it doesn't
- look like it, but it's all due to the magic [hell?] of
- non-local gotos and global variables). At this point,
- the stack is empty, so we can't access any local variables.
- All subsequent executions of the thread will go through the
- setjmp call in ThreadActivate. */
-
- /* restore the context of the thread */
- ThreadRestore();
-
- /* run the thread */
- ThreadStart();
- check(false); /* never returns */
- }
-
- /* Munge the registers in the jump environment so that we start from
- the top of the thread's stack, and so that tracing function calls
- on the stack will stop when it reaches the first function executed
- in the new thread (which will always be ThreadBegin, ensuring that
- the thread library segment is not unloaded by my automatic segment
- unloading routines). */
- (**thread).env[a7] = (long) stack_top;
- (**thread).env[a6] = 0;
-
- /* now that the thread is ready to use, append it to the queue of threads
- so that it can be scheduled for execution */
- gThread.queue = LLHAppend(gThread.queue, thread);
- gThread.count++;
-
- /* We've now successfully created a new thread and set things up so
- that the first time the thread is invoked we'll call ThreadStart.
- We let the application call ThreadYield in its own time to switch
- contexts. In other words, the new thread doesn't start executing
- until it has been scheduled to start. */
-
- } CATCH {
- ThreadEnd(thread);
- } ENDTRY;
- ensure(ThreadValid(thread));
- return(thread);
- }
-